package twig

import (
	"bufio"
	"bytes"
	"encoding/json"
	"io"
	"math/rand"
	"os/exec"
	"sync"
	"time"
)

type Twig struct {
	ProcessPool    map[int]*Process
	ConfRoot       string
	ConfIndexPath  string
	ConfTwigPath   string
	ConfHeader     string
	ConfFooter     string
	ConfCacheDir   string
	ConfDev        bool
	ConfNumPipes   int
	ConfPhpBinPath string
}
type Process struct {
	Cmd             *exec.Cmd
	OutPutReader    *bufio.Reader
	InputPipeReader *io.PipeReader
	InputPipeWriter *io.PipeWriter
	mt              *sync.Mutex
	index           int
}

func (t *Twig) Init() {
	if t.ConfNumPipes == 0 {
		t.ConfNumPipes = 1
	}
	if t.ConfHeader == "" {
		t.ConfHeader = "[[go_php_piper_start]]"
	}
	if t.ConfFooter == "" {
		t.ConfFooter = "[[go_php_piper_finish]]"
	}
	if t.ConfCacheDir == "" {
		t.ConfCacheDir = "var/tmp"
	}

	if t.ConfDev {
		return
	}
	t.ProcessPool = make(map[int]*Process)
	for i := 0; i < t.ConfNumPipes; i++ {
		t.ProcessPool[i] = t.WarmUp(i)
	}
}

func (t *Twig) Render(tpl string, data interface{}) string {
	if t.ConfDev {
		return t.RenderDev(tpl, data)
	} else {
		return t.RenderPipe(tpl, data)
	}
}

func (t *Twig) RenderDev(tpl string, data interface{}) string {
	param := []string{
		t.ConfRoot + t.ConfIndexPath + `/index.php`, //脚本文件位置
		"--tplDir=" + t.ConfRoot + t.ConfTwigPath,   //传递给php用于查找模板目录
		"--dev=1", //开发模式不实用缓存，修改后无需重新编译，生产环境需要清除缓存
		"--cacheDir=" + t.ConfRoot + t.ConfCacheDir,
		"--header=" + t.ConfHeader, //stdin,stdout 值传递的开头和结尾
		"--footer=" + t.ConfFooter, //stdin,stdout 值传递的开头和结尾
	}
	cmd := exec.Command(t.ConfPhpBinPath, param...)
	var config = make(map[string]interface{})
	config["__tplPath"] = tpl
	config["data"] = data
	content, err := json.Marshal(config)
	if err != nil {
		println("json error")
	}
	//判断输入内容,依靠特定的结尾来判断本次输入是否完成
	startFlag := t.ConfHeader
	endFlag := t.ConfFooter
	tmp := startFlag + string(content) + endFlag
	stdin := bytes.NewBuffer([]byte(tmp))
	cmd.Stdin = stdin
	out, err := cmd.Output()
	if err != nil {
		return err.Error()
	}
	return string(out[len(startFlag) : len(out)-len(endFlag)-1])
}

func (t *Twig) WarmUp(index int) *Process {
	param := []string{
		t.ConfRoot + t.ConfIndexPath + `/index.php`, //脚本文件位置
		"--tplDir=" + t.ConfRoot + t.ConfTwigPath,   //传递给php用于查找模板目录
		"--dev=0", //开发模式不实用缓存，修改后无需重新编译，生产环境需要清除缓存
		"--cacheDir=" + t.ConfRoot + t.ConfCacheDir,
		"--header=" + t.ConfHeader, //stdin,stdout 值传递的开头和结尾
		"--footer=" + t.ConfFooter, //stdin,stdout 值传递的开头和结尾
	}
	cmd := exec.Command(t.ConfPhpBinPath, param...)
	pr, pw := io.Pipe()
	cmd.Stdin = pr
	outPipe, err := cmd.StdoutPipe()
	if err != nil {
		println("Cmd StdoutPipe Error :" + err.Error())
	}
	if outPipe == nil {
		println("Cmd StdoutPipe is nil")
	}
	reader := bufio.NewReader(outPipe)
	err = cmd.Start()
	if err != nil {
		println("Cmd Start Error :" + err.Error())
	}
	tv := &Process{cmd, reader, pr, pw, &sync.Mutex{}, index}
	//println("php pipe start " , index)
	return tv
}

func (t *Twig) RenderPipe(tpl string, data interface{}) string {
	if t.ConfDev {
		return t.RenderDev(tpl, data)
	}

	index := rand.Intn(t.ConfNumPipes)
	tv := t.ProcessPool[index]
	tv.mt.Lock()
	defer tv.mt.Unlock()
	var config = make(map[string]interface{})
	config["__tplPath"] = tpl
	config["data"] = data
	content, err := json.Marshal(config)
	if err != nil {
		println("json error")
	}
	//判断输入内容,依靠特定的结尾来判断本次输入是否完成
	startFlag := t.ConfHeader
	endFlag := t.ConfFooter
	tmp := startFlag + string(content) + endFlag
	buf := bytes.NewBufferString(tmp)
	//写入数据
	for {
		towrite := buf.Next(1024)
		if len(towrite) == 0 {
			break
		}
		n, e := tv.InputPipeWriter.Write(towrite)
		if e != nil {
			println("tv.InputPipeWriter.Write", e.Error())
		}
		if n < 1024 {
			//以空格补齐
			toAppend := make([]byte, 0)
			for i := n; i < 1024; i++ {
				toAppend = append(toAppend, ' ')
			}
			_, e := tv.InputPipeWriter.Write(toAppend)
			if e != nil {
				println("tv.InputPipeWriter.Write", e.Error())
			}
		}
	}
	rst := ""
	ch := make(chan string)
	go func() {
		for {
			s, err := tv.OutPutReader.ReadString('\n')
			if len(s) > len(startFlag) && s[0:len(startFlag)] == startFlag {
				rst = ""
			}
			rst += s
			if err != nil {
				println("err", err.Error())
				ch <- "Error " + err.Error()
				break
			}
			if err == io.EOF {
				println("== io.EOF")
				ch <- "Error EOF"
				break
			}
			if len(s) > len(endFlag) && s[len(s)-len(endFlag)-1:len(s)-1] == endFlag {
				ch <- rst[len(startFlag) : len(rst)-len(endFlag)-1]
				break
			}
		}
	}()
	go func() {
		time.Sleep(10 * time.Second)
		ch <- "Error Time Out"
	}()
	d := <-ch
	return d
}
